package log

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"store/bootstrap"
	"store/libs/helper"
	"strings"
	"time"
)

var (
	logger *zap.Logger
)

func Start() *zap.Logger {
	defaultFileMode := os.FileMode(0775)
	if bootstrap.Config.Log.FileMode > 0 {
		defaultFileMode = os.FileMode(bootstrap.Config.Log.FileMode)
	}

	//如果目录不存在
	if !helper.IsDir(helper.DirName(bootstrap.Config.Log.FileName)) {
		//创建目录失败
		if err := os.MkdirAll(helper.DirName(bootstrap.Config.Log.FileName), defaultFileMode); err != nil {
			panic(err.Error())
		}
	}

	file, err := helper.FOpen(bootstrap.Config.Log.FileName, "a", defaultFileMode)
	//打开日志文件失败
	if err != nil {
		panic(err.Error())
	}

	var allCore []zapcore.Core
	fileWriter := zapcore.AddSync(file)
	config := zapcore.EncoderConfig{
		TimeKey:       "ts",
		LevelKey:      "level",
		NameKey:       "logger",
		MessageKey:    "msg",
		StacktraceKey: "stacktrace",
		//行分隔符
		LineEnding: zapcore.DefaultLineEnding,
		//小写字母
		EncodeLevel: zapcore.LowercaseLevelEncoder,
		//使用秒为单位
		EncodeDuration: zapcore.SecondsDurationEncoder,
		// 使用字符串
		// EncodeDuration: zapcore.StringDurationEncoder,
		EncodeCaller: zapcore.ShortCallerEncoder,
		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Format("2006-01-02 15:04:05"))
		},
		CallerKey: "file",
	}

	zap.NewDevelopmentEncoderConfig()
	consoleEncoder := zapcore.NewConsoleEncoder(config)

	allCore = append(allCore, zapcore.NewCore(consoleEncoder, fileWriter, zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.Level(bootstrap.Config.Log.Level)
	})))

	core := zapcore.NewTee(allCore...)
	logger = zap.New(core).WithOptions(zap.AddCaller())
	return logger
}

func LogByZap(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		//设置请求trace
		c.Set("trace-id", start.UnixNano())
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery

		c.Next()

		end := time.Now()
		latency := end.Sub(start)

		fields := FieldsByContext(c)

		if len(c.Errors) > 0 {
			for _, e := range c.Errors.Errors() {
				logger.Error(e, fields...)
			}
		} else {
			fields = append(fields,
				zap.Int("status", c.Writer.Status()),
				zap.String("query", query),
				zap.Duration("latency", latency),
			)

			logger.Info(path, fields...)
		}
	}
}

func RecoveryWithZap(logger *zap.Logger, stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				fields := FieldsByContext(c)
				fields = append(fields, zap.Any("error", err))

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					logger.Error(c.Request.URL.Path, fields...)
					c.Error(err.(error))
					c.Abort()
					return
				}

				if stack {
					fields = append(fields, zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())))
					logger.Error("[Recovery from panic]", fields...)
				} else {
					fields = append(fields, zap.String("request", string(httpRequest)))
					logger.Error("[Recovery from panic]", fields...)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

func FieldsByContext(ctx *gin.Context) []zap.Field {
	fields := []zap.Field{}
	if ctx == nil {
		return fields
	}

	fields = append(fields,
		zap.String("method", ctx.Request.Method),
		zap.String("ip", ctx.ClientIP()),
		zap.String("user-agent", ctx.Request.UserAgent()),
		zap.Int64("trace-id", helper.GetTraceId(ctx)),
	)

	return fields
}

func Debug(ctx *gin.Context, msg string, fields ...zap.Field) {
	ctxFields := FieldsByContext(ctx)
	ctxFields = append(ctxFields, fields...)
	logger.Debug(msg, ctxFields...)
}

func Info(ctx *gin.Context, msg string, fields ...zap.Field) {
	ctxFields := FieldsByContext(ctx)
	ctxFields = append(ctxFields, fields...)
	logger.Info(msg, ctxFields...)
}

func Warn(ctx *gin.Context, msg string, fields ...zap.Field) {
	ctxFields := FieldsByContext(ctx)
	ctxFields = append(ctxFields, fields...)
	logger.Warn(msg, ctxFields...)
}

func Error(ctx *gin.Context, msg string, fields ...zap.Field) {
	ctxFields := FieldsByContext(ctx)
	ctxFields = append(ctxFields, fields...)
	logger.Error(msg, ctxFields...)
}

/**
 * PanicLevel logs a message, then panics.
 */
func Panic(ctx *gin.Context, msg string, fields ...zap.Field) {
	ctxFields := FieldsByContext(ctx)
	ctxFields = append(ctxFields, fields...)
	logger.Panic(msg, ctxFields...)
}

/**
 * FatalLevel logs a message, then calls os.Exit(1).
 */
func Fatal(ctx *gin.Context, msg string, fields ...zap.Field) {
	ctxFields := FieldsByContext(ctx)
	ctxFields = append(ctxFields, fields...)
	logger.Fatal(msg, ctxFields...)
}
